#pragma once
namespace graph_basic
{
namespace details
{   
   inline graph_mutable_t::graph_mutable_t(graph_inf_t inf)
      :  inf_(inf)
   {
      adj_list_.reserve(inf_.vertex_count_);
   }

   inline void graph_mutable_t::make_sequential(graph_sequential_ptr_t& result, size_t& size)
   { 
      std::vector<vertex_t> vertices;
      std::vector<details::edge_real_t> edges;
      
      size_t adj_list_offset = 0;
      for(size_t i = 0; i < adj_list_.size(); ++i)
      {
         vertex_t v(
            vertex_id_t(adj_list_[i].get_id().graph_id_, adj_list_[i].get_id().vertex_in_graph_id_), 
            adj_list_[i].get_point(),
            adj_list_offset);
         vertices.push_back(v);         
         
         for(size_t j = 0; j < adj_list_[i].get_adj_edges().size(); ++j)
         {
            edges.push_back(adj_list_[i].get_adj_edges()[j]);
         }
         adj_list_offset += adj_list_[i].get_adj_edges().size();
      }
      
      size = sizeof(details::graph_inf_t) +
             sizeof(vertex_t) * vertices.size() +
             sizeof(edge_t) * edges.size();
             
      graph_sequential_t* graph = static_cast<graph_sequential_t*>(malloc(size));
      graph->inf_.id_ = inf_.id_;
      graph->inf_.vertex_count_ = vertices.size();
      graph->inf_.edge_count_ = edges.size();
      graph->inf_.max_lat_ = inf_.max_lat_;
      graph->inf_.min_lat_ = inf_.min_lat_;
      graph->inf_.max_lon_ = inf_.max_lon_;
      graph->inf_.min_lon_ = inf_.min_lon_;
      
      vertex_t* begin_of_vertices = reinterpret_cast<vertex_t*>(graph->data_);                  
      for(size_t i = 0; i < vertices.size(); ++i)
      {
         begin_of_vertices[i] = vertices[i];
      } 
      
      details::edge_real_t* begin_of_edges = reinterpret_cast<details::edge_real_t*>(begin_of_vertices + vertices.size());    
      for(size_t i = 0; i < edges.size(); ++i)
      {
         begin_of_edges[i] = edges[i];
      }
      
      result = graph_sequential_ptr_t(graph);
   }
   inline void graph_mutable_t::make_not_sequential(graph_not_sequential_ptr_t& result, 
                                                    vertex_t** vertices, 
                                                    edge_real_t** edges)
   { 
      std::vector<vertex_t> vertices_temp;
      std::vector<details::edge_real_t> edges_temp;
      
      size_t adj_list_offset = 0;
      for(size_t i = 0; i < adj_list_.size(); ++i)
      {
         vertex_t v(
            vertex_id_t(adj_list_[i].get_id().graph_id_, adj_list_[i].get_id().vertex_in_graph_id_),
            adj_list_[i].get_point(),
            adj_list_offset
            );
         vertices_temp.push_back(v);         
         
         for(size_t j = 0; j < adj_list_[i].get_adj_edges().size(); ++j)
         {
            edges_temp.push_back(adj_list_[i].get_adj_edges()[j]);
         }
         adj_list_offset += adj_list_[i].get_adj_edges().size();
      }
      
      *vertices = static_cast<vertex_t*>(malloc(sizeof(vertex_t) * vertices_temp.size()));
      *edges = static_cast<details::edge_real_t*>(malloc(sizeof(details::edge_real_t) * edges_temp.size()));
                      
      for(size_t i = 0; i < vertices_temp.size(); ++i)
      {
         (*vertices)[i] = vertices_temp[i];
      } 
      
      for(size_t i = 0; i < edges_temp.size(); ++i)
      {
         (*edges)[i] = edges_temp[i];
      }
      inf_.vertex_count_ = vertices_temp.size();
      inf_.edge_count_ = edges_temp.size();
      //inf_.max_lat_ = inf_.max_lat_;
      //inf_.min_lat_ = inf_.min_lat_;
      //inf_.max_lon_ = inf_.max_lon_;
      //inf_.min_lon_ = inf_.min_lon_;
      result = graph_not_sequential_ptr_t(
         new graph_not_sequential_t(inf_, *vertices, *edges)
      );
   }


   inline std::vector<vertex_mutable_t>& graph_mutable_t::get_adj_list()
   {
      return adj_list_;
   }

   inline vertex_mutable_t& graph_mutable_t::add_vertex(point_t p)
   {
      details::vertex_mutable_t new_v(vertex_id_t(inf_.id_, adj_list_.size()),p);
      adj_list_.push_back(new_v);
      return adj_list_.back();
   }
}
//using namespace math;
//graph_base_t
inline graph_id_t graph_base_t::get_graph_id() const
{
   return inf_.id_;
}
inline size_t graph_base_t::get_vertex_count() const
{
   return inf_.vertex_count_;
}
inline size_t graph_base_t::get_edge_count() const
{
   return inf_.edge_count_;
}
inline lat_lon_t graph_base_t::get_min_lat() const
{
   return inf_.min_lat_;
}
inline lat_lon_t graph_base_t::get_min_lon() const
{
   return inf_.min_lon_;
}
inline lat_lon_t graph_base_t::get_max_lat() const
{
   return inf_.max_lat_;
}
inline lat_lon_t graph_base_t::get_max_lon() const
{
   return inf_.max_lon_;
} 

//graph_sequential_t
inline vertex_t const & graph_sequential_t::operator [] (size_t idx) const
{
   return *(reinterpret_cast<vertex_t*>( const_cast<char(*)[]>(&data_) ) + idx);
}
inline vertex_t& graph_sequential_t::operator [] (size_t idx)
{
   return *(reinterpret_cast<vertex_t*>( const_cast<char(*)[]>(&data_) ) + idx);
}
inline std::pair<details::edge_real_t*, details::edge_real_t*> 
   graph_sequential_t::get_vertex_edges(vertex_in_graph_id_t idx) const
{
   static details::edge_real_t* begin_of_edges_ = reinterpret_cast<details::edge_real_t*>(
            reinterpret_cast<vertex_t*>( const_cast<char(*)[]>(&data_) ) + inf_.vertex_count_
         );
   
   return std::make_pair(
      begin_of_edges_ + (*this)[idx].get_adj_list_offset(),
      (
         idx + 1 != inf_.vertex_count_ 
         ?  begin_of_edges_ + (*this)[idx + 1].get_adj_list_offset()
         :  begin_of_edges_ + inf_.edge_count_
      )
   );
}   

//graph_not_sequential_t
inline vertex_t const & graph_not_sequential_t::operator [] (size_t idx) const
{
   return vertices_[idx];
}   
inline vertex_t& graph_not_sequential_t::operator [] (size_t idx) 
{
   return vertices_[idx];
}  

inline std::pair<details::edge_real_t*, details::edge_real_t*> graph_not_sequential_t::get_vertex_edges(vertex_in_graph_id_t idx) const
{     
   return std::make_pair(
      edges_ + (*this)[idx].get_adj_list_offset(),
      (
         idx + 1 != inf_.vertex_count_ 
         ?  edges_ + (*this)[idx + 1].get_adj_list_offset()
         :  edges_ + inf_.edge_count_
      )
   );
}   
template<class G>
inline vertex_id_pair_t nearest_vertices(G& graph, point_t first, point_t second)
{
   using namespace math;
    std::pair<graph_traits<G>::vertex_iterator, graph_traits<G>::vertex_iterator> it_pair = vertices(graph);

    vertex_id_t nearest_first_vertex_id = (*it_pair.first).get_id();
    vertex_id_t nearest_second_vertex_id = (*it_pair.first).get_id();

    point_t first_lat_lon = (*it_pair.first).get_point();
    point_t second_lat_lon = (*it_pair.first).get_point();
    point_t temp_lat_lon;
    
    float first_distance = 
        distance(first.lat_,
                 first.lat_,
                 first_lat_lon.lat_,
                 first_lat_lon.lon_); 

    float second_distance = 
        distance(second.lat_,
                 second.lat_,
                 second_lat_lon.lat_,
                 second_lat_lon.lon_); 

    float temp_lat = 0;
    float temp_lng = 0;
    float temp_distance_first_vertex = 0;
    float temp_distance_second_vertex = 0;
    for(; it_pair.first != it_pair.second; ++it_pair.first) 
    { 
        temp_lat_lon = (*it_pair.first).get_point(); 

        temp_lat = temp_lat_lon.lat_; 
        temp_lng = temp_lat_lon.lon_;
        temp_distance_first_vertex = distance(first.lat_, first.lon_, temp_lat, temp_lng);
        temp_distance_second_vertex = distance(second.lat_, second.lon_, temp_lat, temp_lng);

        if ( temp_distance_first_vertex < first_distance)
        {
            first_lat_lon = temp_lat_lon;
            first_distance = temp_distance_first_vertex; 
            nearest_first_vertex_id = (*it_pair.first).get_id();
        }
        if ( temp_distance_second_vertex < second_distance)
        {
            second_lat_lon = temp_lat_lon;
            second_distance = temp_distance_second_vertex; 
            nearest_second_vertex_id = (*it_pair.first).get_id();
        }
    }
    return vertex_id_pair_t(nearest_first_vertex_id, nearest_second_vertex_id);
}

template<class G>
inline vertex_id_t nearest_vertex(G& graph, point_t first)
{
    using namespace math;
    std::pair<graph_traits<G>::vertex_iterator, graph_traits<G>::vertex_iterator> it_pair = vertices(graph);

    vertex_id_t nearest_first_vertex_id = (*it_pair.first).get_id();
    vertex_id_t nearest_second_vertex_id = (*it_pair.first).get_id();

    point_t first_lat_lon = (*it_pair.first).get_point();
    point_t temp_lat_lon;
    
    float first_distance = 
        distance(first.lat_,
                 first.lat_,
                 first_lat_lon.lat_,
                 first_lat_lon.lon_); 

    float temp_lat = 0;
    float temp_lng = 0;
    float temp_distance_first_vertex = 0;
    for(; it_pair.first != it_pair.second; ++it_pair.first) 
    { 
        temp_lat_lon = (*it_pair.first).get_point(); 

        temp_lat = temp_lat_lon.lat_; 
        temp_lng = temp_lat_lon.lon_;
        temp_distance_first_vertex = distance(first.lat_, first.lon_, temp_lat, temp_lng);

        if ( temp_distance_first_vertex < first_distance)
        {
            first_lat_lon = temp_lat_lon;
            first_distance = temp_distance_first_vertex; 
            nearest_first_vertex_id = (*it_pair.first).get_id();
        }
    }
    return nearest_first_vertex_id;
}
inline vertex_t& source(edge_t e, graph_t& g)
{
    return g[e.get_source_vertex_id().vertex_in_graph_id_];
}

inline vertex_t const & source(edge_t const e, graph_t const & g)
{
   return g[e.get_source_vertex_id().vertex_in_graph_id_];
}

inline vertex_t& target(edge_t e, graph_t& g)
{
   return g[e.get_target_vertex_id().vertex_in_graph_id_];
}
inline vertex_t const & target(edge_t const e, graph_t const & g)
{
   return g[e.get_target_vertex_id().vertex_in_graph_id_];
}
}
